<?php

/**
 * latlng转换器：从wgs84转换到gcj02
 *
 * 此处转换仅针对互联网民用地图，并假定：
 * 无测定时间（wg_week,wg_time永远为0），地图原点(0,0)（wg_flag永远为1）
 *
 * conf_random为true时，返回结果随机偏移。若要发布到外网服务，必须随机偏移！
 *
 * 验证方法可以从高德地图中验证。验证url：
 * http://mo.amap.com/?q=纬度,经度&name=park&dev=0#thirdpoi
 *
 * 也有在线转换方法，文档见：document/latlng_conv_online_wgs84_2_gcj02.txt
 *
 * @link http://blog.csdn.net/coolypf/article/details/8686588
 * @link https://on4wp7.codeplex.com/SourceControl/changeset/view/21483#353936
 * @author wgtochina_lb
 * @author coolypf
 * @author hl
 * @version 0.1
 *
 */
class MY_Convlatlng
{

    /**
     * @link http://bbs.esrichina-bj.cn/esri/viewthread.php?tid=65098
     * @var float
     */
    const DIMS_FULL_SCENE = 3686400.0;

    /**
     * Krasovsky 1940 (北京54)椭球长半轴
     * @var float
     */
    const ELLIPSOID_PARAM_A = 6378245.0;

    /**
     * Krasovsky 1940 (北京54)椭球长半轴第一偏心率平方
     * 计算方式：
     * 长半轴：
     * a = 6378245.0
     * 扁率：
     * 1/f = 298.3（变量相关计算为：(a-b)/a）
     * 短半轴：
     * b = 6356863.0188 (变量相关计算方法为：b = a * (1 - f))
     * 第一偏心率平方:
     * e2 = (a^2 - b^2) / a^2;
     */
    const ELLIPSOID_PARAM_E2 = 0.00669342162296594323;

    /**
     * 输出的中间量需要random混淆？
     * 如果要对外暴露使用，必须为true
     * @var bool
     */
    static protected $conf_random = true;

    /**
     * random值。self::$conf_random有效时使用
     * 初始值：0或者0.3
     * @var float
     */
    static protected $casm_rr = 0;

    /**
     * pi除以某个系数的结果
     * @var array
     */
    static protected $pi_div_result = array();


    /**
     * pi乘以某个系数的结果
     * @var array
     */
    static protected $pi_mul_result = array();

    /**
     * 转换接口
     * @param float|array $lat wgs84坐标系的纬度、或者array('lat'=>lat, 'lng'=>lng)
     * @param float $lng wgs84坐标系的经度
     * @param int $wg_heit wgs84坐标系的高度。缺省为0
     * @param int $random 输出的中间量需要random混淆？默认为0，表示跟随配置；-1表示强制禁用random混淆；其余数值表示强制启用random混淆
     * @return array 转换后的gcj02坐标(高德坐标系)。格式array('lat'=>lat, 'lng'=>lng)
     */
    static public function conv($lat, $lng = 0, $wg_heit = 0, $random = 0)
    {
        if (is_array($lat)) {
            $lng = isset($lat['lng']) ? $lat['lng'] : 0;
            $lat = isset($lat['lat']) ? $lat['lat'] : 0;
        }

        if (!self::is_conv_range($lat, $lng, $wg_heit)) {
            return array(
                'lat' => $lat,
                'lng' => $lng,
            );
        }

        $x_l = $lng;
        $y_l = $lat;

        //此处直入
        $x_add = self::Transform_yj5($x_l - 105, $y_l - 35);
        $y_add = self::Transform_yjy5($x_l - 105, $y_l - 35);
        $h_add = $wg_heit;

        //由于猜测$wg_time = 0（个人猜测，应该是全部等于0，否则数据迁移很难搞...），后面的计算可省略
        $x_add = $x_add + $h_add * 0.001 /* + self::yj_sin2($wg_time*self::pi_div(180)) */
        ;
        $y_add = $y_add + $h_add * 0.001 /* + self::yj_sin2($wg_time*self::pi_div(180)) */
        ;

        if (0 == $random) {
            if (false != self::$conf_random) {
                $x_add += self::random_yj();
                $y_add += self::random_yj();
            }
        } elseif ($random != -1) {
            $x_add += self::random_yj();
            $y_add += self::random_yj();
        }

        return array(
            'lat' => ($y_l + self::Transform_jyj5($y_l, $y_add)),
            'lng' => ($x_l + self::Transform_jy5($y_l, $x_add)),
        );

    }

    /**
     * 是在需要转换的范畴么？
     * @param float $lat
     * @param float $lng
     * @return bool 不需要转换时，返回false
     */
    static public function is_conv_range($lat, $lng, $wg_heit = 0)
    {
        if ($lng < 72.004 || $lng > 137.8347) {
            return false;
        }

        if ($lat < 0.8293 || $lat > 55.8271) {
            return false;
        }

        if ($wg_heit > 5000) {
            return false;
        }

        return true;

    }

    static public function Transform_yj5($x, $y)
    {
        $tt = 300 + $x + 2 * $y + 0.1 * pow($x, 2) + 0.1 * $x * $y + 0.1 * sqrt(abs($x));

        $tt += (20 * self::yj_sin2(self::pi_mul(6) * $x) + 20 * self::yj_sin2(self::pi_mul(2) * $x)) * 2.0 / 3.0;
        $tt += (20 * self::yj_sin2(M_PI * $x) + 40 * self::yj_sin2(self::pi_div(3) * $x)) * 2.0 / 3.0;
        $tt += (150 * self::yj_sin2(self::pi_div(12) * $x) + 300 * self::yj_sin2(self::pi_div(30) * $x)) * 2.0 / 3.0;
        return $tt;
    }

    static public function Transform_yjy5($x, $y)
    {
        $tt = -100 + 2 * $x + 3 * $y + 0.2 * pow($y, 2) + 0.1 * $x * $y + 0.2 * sqrt(abs($x));
        $tt = $tt + (20 * self::yj_sin2(self::pi_mul(6) * $x) + 20 * self::yj_sin2(self::pi_mul(2) * $x)) * 2.0 / 3.0;
        $tt = $tt + (20 * self::yj_sin2(M_PI * $y) + 40 * self::yj_sin2(self::pi_div(3) * $y)) * 2.0 / 3.0;
        $tt = $tt + (160 * self::yj_sin2(self::pi_div(12) * $y) + 320 * self::yj_sin2(self::pi_div(30) * $y)) * 2.0 / 3.0;
        return $tt;
    }

    /**
     *
     * @param float $x
     * @return float
     */
    static public function yj_sin2($x)
    {

        //另一种是直接返回sin($x)
        //return sin($x);

        $tt = $ss = $s2 = 0.0;
        $ff = 0;

        if ($x < 0) {
            $x *= -1;
            $ff = 1;
        }

        $tt = $x - intval($x / self::pi_mul(2)) * self::pi_mul(2);    //如果不考虑intval，$tt必定等于0

        if ($tt > M_PI) {
            $tt -= M_PI;
            if ($ff == 1) {
                $ff = 0;
            } elseif ($ff == 0) {
                $ff = 1;
            }
        }

        //中间量若为0，则所有后续运算全部归0
        if (0 == $tt) {
            return 0;
        }

        $x = $ss = $s2 = $tt;
        $tt = pow($tt, 2);

        //分别为3!,5!,7!,9!,11!
        foreach (array(6, 120, 5040, 362880, 39916800) as $index => $value) {
            $s2 *= $tt;
            if (0 == $index % 2) {
                $ss -= $s2 / $value;
            } else {
                $ss += $s2 / $value;
            }
        }

        if ($ff == 1) {
            $ss *= -1;
        }
        return $ss;

    }

    static public function random_yj()
    {
        $casm_rr = self::$casm_rr;

        $casm_rr = 314159269 * $casm_rr + 453806245;
        $casm_rr = $casm_rr - intval($casm_rr / 2) * 2;
        $casm_rr = $casm_rr / 2;

        self::$casm_rr = $casm_rr;
        return $casm_rr;
    }

    static public function Transform_jyj5($x, $yy)
    {
        $mm = 1 - self::ELLIPSOID_PARAM_E2 * self::yj_sin2($x * self::pi_div(180)) * self::yj_sin2($x * self::pi_div(180));
        $m = (self::ELLIPSOID_PARAM_A * (1 - self::ELLIPSOID_PARAM_E2)) / ($mm * sqrt($mm));
        return ($yy * 180) / ($m * M_PI);
    }

    static public function Transform_jy5($x, $xx)
    {
        $n = sqrt(1 - self::ELLIPSOID_PARAM_E2 * self::yj_sin2($x * self::pi_div(180)) * self::yj_sin2($x * self::pi_div(180)));
        $n = ($xx * 180) / (self::ELLIPSOID_PARAM_A / $n * cos($x * self::pi_div(180)) * M_PI);
        return $n;
    }

    /**
     * pi除以
     * @param int $x
     */
    static public function pi_div($x = 1)
    {
        if (!isset(self::$pi_div_result[$x])) {
            self::$pi_div_result[$x] = (1 == $x) ? M_PI : (M_PI / $x);
        }
        return self::$pi_div_result[$x];
    }


    /**
     * pi乘以
     * @param int $x
     * @return float
     */
    static public function pi_mul($x = 1)
    {
        if (!isset(self::$pi_mul_result[$x])) {
            self::$pi_mul_result[$x] = (1 == $x) ? M_PI : (M_PI * $x);
        }
        return self::$pi_mul_result[$x];
    }

    /**
     * 获取或者设置conf_random的值
     * @param bool|null $val 为bool值时，表示要设置为对应值；其余情况下，返回当前设置值
     * @return bool
     */
    static public function conf_random($val = null)
    {
        if (!is_bool($val)) {
            return self::$conf_random;
        }
        self::$conf_random = $val;
        return $val;
    }

}
